// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "cc/surfaces/direct_compositor_frame_sink.h"

#include "base/bind.h"
#include "cc/output/compositor_frame.h"
#include "cc/output/compositor_frame_sink_client.h"
#include "cc/surfaces/display.h"
#include "cc/surfaces/frame_sink_id.h"
#include "cc/surfaces/surface.h"
#include "cc/surfaces/surface_id_allocator.h"
#include "cc/surfaces/surface_manager.h"

namespace cc {

DirectCompositorFrameSink::DirectCompositorFrameSink(
    const FrameSinkId& frame_sink_id,
    SurfaceManager* surface_manager,
    Display* display,
    scoped_refptr<ContextProvider> context_provider,
    scoped_refptr<ContextProvider> worker_context_provider,
    gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager,
    SharedBitmapManager* shared_bitmap_manager)
    : CompositorFrameSink(std::move(context_provider),
        std::move(worker_context_provider),
        gpu_memory_buffer_manager,
        shared_bitmap_manager)
    , frame_sink_id_(frame_sink_id)
    , surface_manager_(surface_manager)
    , display_(display)
    , factory_(frame_sink_id, surface_manager, this)
{
    DCHECK(thread_checker_.CalledOnValidThread());
    capabilities_.can_force_reclaim_resources = true;
    // Display and DirectCompositorFrameSink share a GL context, so sync
    // points aren't needed when passing resources between them.
    capabilities_.delegated_sync_points_required = false;
    factory_.set_needs_sync_points(false);
}

DirectCompositorFrameSink::DirectCompositorFrameSink(
    const FrameSinkId& frame_sink_id,
    SurfaceManager* surface_manager,
    Display* display,
    scoped_refptr<VulkanContextProvider> vulkan_context_provider)
    : CompositorFrameSink(std::move(vulkan_context_provider))
    , frame_sink_id_(frame_sink_id)
    , surface_manager_(surface_manager)
    , display_(display)
    , factory_(frame_sink_id_, surface_manager, this)
{
    DCHECK(thread_checker_.CalledOnValidThread());
    capabilities_.can_force_reclaim_resources = true;
}

DirectCompositorFrameSink::~DirectCompositorFrameSink()
{
    DCHECK(thread_checker_.CalledOnValidThread());
}

bool DirectCompositorFrameSink::BindToClient(
    CompositorFrameSinkClient* client)
{
    DCHECK(thread_checker_.CalledOnValidThread());

    if (!CompositorFrameSink::BindToClient(client))
        return false;

    surface_manager_->RegisterSurfaceFactoryClient(frame_sink_id_, this);

    // We want the Display's output surface to hear about lost context, and since
    // this shares a context with it, we should not be listening for lost context
    // callbacks on the context here.
    if (auto* cp = context_provider())
        cp->SetLostContextCallback(base::Closure());

    // Avoid initializing GL context here, as this should be sharing the
    // Display's context.
    display_->Initialize(this, surface_manager_);
    return true;
}

void DirectCompositorFrameSink::DetachFromClient()
{
    // Unregister the SurfaceFactoryClient here instead of the dtor so that only
    // one client is alive for this namespace at any given time.
    surface_manager_->UnregisterSurfaceFactoryClient(frame_sink_id_);
    factory_.EvictSurface();

    CompositorFrameSink::DetachFromClient();
}

void DirectCompositorFrameSink::SubmitCompositorFrame(CompositorFrame frame)
{
    gfx::Size frame_size = frame.render_pass_list.back()->output_rect.size();
    if (frame_size.IsEmpty() || frame_size != last_swap_frame_size_) {
        delegated_local_frame_id_ = surface_id_allocator_.GenerateId();
        last_swap_frame_size_ = frame_size;
    }
    display_->SetLocalFrameId(delegated_local_frame_id_,
        frame.metadata.device_scale_factor);

    factory_.SubmitCompositorFrame(
        delegated_local_frame_id_, std::move(frame),
        base::Bind(&DirectCompositorFrameSink::DidDrawCallback,
            base::Unretained(this)));
}

void DirectCompositorFrameSink::ForceReclaimResources()
{
    if (delegated_local_frame_id_.is_valid())
        factory_.ClearSurface();
}

void DirectCompositorFrameSink::ReturnResources(
    const ReturnedResourceArray& resources)
{
    if (client_)
        client_->ReclaimResources(resources);
}

void DirectCompositorFrameSink::SetBeginFrameSource(
    BeginFrameSource* begin_frame_source)
{
    DCHECK(client_);
    client_->SetBeginFrameSource(begin_frame_source);
}

void DirectCompositorFrameSink::DisplayOutputSurfaceLost()
{
    is_lost_ = true;
    client_->DidLoseCompositorFrameSink();
}

void DirectCompositorFrameSink::DisplayWillDrawAndSwap(
    bool will_draw_and_swap,
    const RenderPassList& render_passes)
{
    // This notification is not relevant to our client outside of tests.
}

void DirectCompositorFrameSink::DisplayDidDrawAndSwap()
{
    // This notification is not relevant to our client outside of tests. We
    // unblock the client from DidDrawCallback() when the surface is going to
    // be drawn.
}

void DirectCompositorFrameSink::DidDrawCallback()
{
    client_->DidReceiveCompositorFrameAck();
}

} // namespace cc
